/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.picasso;

import ohos.agp.components.ComponentProvider;
import ohos.agp.components.Image;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.rpc.RemoteException;
import ohos.utils.net.Uri;

import java.io.File;
import java.lang.ref.ReferenceQueue;
import java.util.*;
import java.util.concurrent.ExecutorService;

import static com.squareup.picasso.Dispatcher.*;
import static com.squareup.picasso.MemoryPolicy.shouldReadFromMemoryCache;
import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
import static com.squareup.picasso.Utils.*;


public class Picasso {

    public interface RequestTransformer {
        /**
         * Transform a request before it is submitted to be processed.
         *
         * @param request the request Transform a request before it is submitted to be processed.
         * @return The original request or a new request to replace it. Must not be null.
         */
        Request transformRequest(Request request);

        /**
         * A {@link RequestTransformer} which returns the original request.
         */
        RequestTransformer IDENTITY = new RequestTransformer() {
            @Override public Request transformRequest(Request request) {
                return request;
            }
        };
    }

    public interface Listener {
        /**
         * On image load failed.
         *
         * @param picasso   the picasso
         * @param uri       the uri
         * @param exception the exception Invoked when an image has failed to load. This is useful for reporting image failures to a remote analytics service, for example.
         */
        void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception);
    }

    private final Listener listener;
    private final RequestTransformer requestTransformer;
    private final List<RequestHandler> requestHandlers;
    private final CleanupThread cleanupThread;
    private static volatile Picasso singleton = null;
    private final Map<Image, DeferredRequestCreator> targetToDeferredRequestCreator;
    private final Map<Object, Action> targetToAction;


    final Stats stats;
    final Dispatcher dispatcher;
    final Context context;
    final Cache cache;
    final ReferenceQueue<Object> referenceQueue;
    volatile boolean loggingEnabled;
    PixelFormat defaultBitmapConfig;
    boolean shutdown;
    boolean indicatorsEnabled;

    /**
     * Instantiates a new Picasso.
     *
     * @param context              the context
     * @param dispatcher           the dispatcher
     * @param cache                the cache
     * @param listener             the listener
     * @param requestTransformer   the request transformer
     * @param extraRequestHandlers the extra request handlers
     * @param stats                the stats
     * @param defaultBitmapConfig  the default bitmap config
     * @param indicatorsEnabled    the indicators enabled
     * @param loggingEnabled       the logging enabled
     */
    public Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
                   RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
                   PixelFormat defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
        this.listener=listener;
        this.context = context;
        this.referenceQueue = new ReferenceQueue<>();
        this.targetToDeferredRequestCreator = new WeakHashMap<>();
        this.cache = cache;
        this.targetToAction = new WeakHashMap<>();
        this.dispatcher = dispatcher;
        this.requestTransformer = requestTransformer;
        this.indicatorsEnabled = indicatorsEnabled;
        this.loggingEnabled = loggingEnabled;

        int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
        int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);

        List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
        allRequestHandlers.add(new ContentStreamRequestHandler(context));
        allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
        requestHandlers = Collections.unmodifiableList(allRequestHandlers);

        this.stats = stats;
        this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
        this.cleanupThread.start();
    }

    /**
     * When the target of an action is weakly reachable but the request hasn't been canceled, it
     * gets added to the reference queue. This thread empties the reference queue and cancels the
     * request.
     */
    private static class CleanupThread extends Thread {
        private final ReferenceQueue<Object> referenceQueue;
        private final EventHandler handler;

        /**
         * Instantiates a new Cleanup thread.
         *
         * @param referenceQueue the reference queue
         * @param handler        the handler
         */
        CleanupThread(ReferenceQueue<Object> referenceQueue, EventHandler handler) {
            this.referenceQueue = referenceQueue;
            this.handler = handler;
            setDaemon(true);
            setName(THREAD_PREFIX + "refQueue");
        }

        @Override public void run() {
        }

        /**
         * Shutdown.
         */
        void shutdown() {
            interrupt();
        }
    }

    /**
     * Gets request handlers.
     *
     * @return the request handlers
     */
    List<RequestHandler> getRequestHandlers() {
        return requestHandlers;
    }

    /**
     * The global {@link Picasso} instance.
     * <p>
     * This instance is automatically initialized with defaults that are suitable to most
     * implementations.
     * <ul>
     * <li>LRU memory cache of 15% the available application RAM</li>
     * <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only
     * available on API 14+ <em>or</em> if you are using a standalone library that provides a disk
     * cache on all API levels like OkHttp)</li>
     * <li>Three download threads for disk and network access.</li>
     * </ul>
     * <p>
     * If these settings do not meet the requirements of your application you can construct your own
     * with full control over the configuration by using {@link Builder} to create a
     * {@link Picasso} instance. You can either use this directly or by setting it as the global
     * instance with {@setSingletonInstance}.
     *
     * @return the picasso
     */
    public static Picasso get() {
        if (singleton == null) {
            synchronized (Picasso.class) {
                if (singleton == null) {
                    if (PicassoProvider.context == null) {
                        throw new IllegalStateException("context == null");
                    }
                    singleton = new Builder(PicassoProvider.context).build();
                }
            }
        }
        return singleton;
    }

    /**
     * Set the global instance returned from {@link #get}.
     * <p>
     * This method must be called before any calls to {@link #get} and may only be called once.
     *
     * @param picasso the picasso
     */
    public static void setSingletonInstance( Picasso picasso) {

        if (picasso == null) {
            throw new IllegalArgumentException("Picasso must not be null.");
        }
        synchronized (Picasso.class) {
            if (singleton != null) {
                throw new IllegalStateException("Singleton instance already exists.");
            }
            singleton = picasso;
        }
    }

    /**
     * Fluent API for creating {@link Picasso} instances.
     */
    @SuppressWarnings("UnusedDeclaration") // Public API.
    public static class Builder {
        private final Context context;
        private boolean indicatorsEnabled;
        private boolean loggingEnabled;
        private Downloader downloader;
        private Cache cache;
        private RequestTransformer transformer;
        private Listener listener;
        private PixelFormat defaultBitmapConfig;
        private List<RequestHandler> requestHandlers;
        private ExecutorService service;


        /**
         * Specify the {@link Downloader} that will be used for downloading images.
         *
         * @param downloader the downloader
         * @return the builder
         */
        public Builder downloader(Downloader downloader) {
            if (downloader == null) {
                throw new IllegalArgumentException("Downloader must not be null.");
            }
            if (this.downloader != null) {
                throw new IllegalStateException("Downloader already set.");
            }
            this.downloader = downloader;
            return this;
        }

        /**
         * Specify a transformer for all incoming requests.
         * <p>
         * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
         * way at any time.
         *
         * @param transformer the transformer
         * @return the builder
         */
        public Builder requestTransformer( RequestTransformer transformer) {
            if (transformer == null) {
                throw new IllegalArgumentException("Transformer must not be null.");
            }
            if (this.transformer != null) {
                throw new IllegalStateException("Transformer already set.");
            }
            this.transformer = transformer;
            return this;
        }

        /**
         * Register a {@link RequestHandler}.
         *
         * @param requestHandler the request handler
         * @return the builder
         */
        public Builder addRequestHandler( RequestHandler requestHandler) {
            if (requestHandler == null) {
                throw new IllegalArgumentException("RequestHandler must not be null.");
            }
            if (requestHandlers == null) {
                requestHandlers = new ArrayList<>();
            }
            if (requestHandlers.contains(requestHandler)) {
                throw new IllegalStateException("RequestHandler already registered.");
            }
            requestHandlers.add(requestHandler);
            return this;
        }

        /**
         * Toggle whether to display debug indicators on images.
         *
         * @param enabled the enabled
         * @return the builder
         */
        public Builder indicatorsEnabled(boolean enabled) {
            this.indicatorsEnabled = enabled;
            return this;
        }

        /**
         * Toggle whether debug logging is enabled.
         * <p>
         * <b>WARNING:</b> Enabling this will result in excessive object allocation. This should be only
         * be used for debugging purposes. Do NOT pass {@code BuildConfig.DEBUG}.
         *
         * @param enabled the enabled
         * @return the builder
         */
        public Builder loggingEnabled(boolean enabled) {
            this.loggingEnabled = enabled;
            return this;
        }

        /**
         * Specify a listener for interesting events.
         *
         * @param listener the listener
         * @return the builder
         */
        public Builder listener( Listener listener) {
            if (listener == null) {
                throw new IllegalArgumentException("Listener must not be null.");
            }
            if (this.listener != null) {
                throw new IllegalStateException("Listener already set.");
            }
            this.listener = listener;
            return this;
        }

        /**
         * Specify the memory cache used for the most recent images.
         *
         * @param memoryCache the memory cache
         * @return the builder
         */
        public Builder memoryCache( Cache memoryCache) {
            if (memoryCache == null) {
                throw new IllegalArgumentException("Memory cache must not be null.");
            }
            if (this.cache != null) {
                throw new IllegalStateException("Memory cache already set.");
            }
            this.cache = memoryCache;
            return this;
        }

        /**
         * Specify the executor service for loading images in the background.
         * <p>
         * Note: Calling {@link Picasso#shutdown() shutdown()} will not shutdown supplied executors.
         *
         * @param executorService the executor service
         * @return the builder
         */
        public Builder executor( ExecutorService executorService) {
            if (executorService == null) {
                throw new IllegalArgumentException("Executor service must not be null.");
            }
            if (this.service != null) {
                throw new IllegalStateException("Executor service already set.");
            }
            this.service = executorService;
            return this;
        }

        /**
         * Specify the default {link Bitmap.Config} used when decoding images. This can be overridden
         * on a per-request basis using {@lin RequestCreator#config(Bitmap.Config) config(..)}.
         *
         * @param bitmapConfig the bitmap config
         * @return the builder
         */
        public Builder defaultBitmapConfig( PixelFormat bitmapConfig) {
            if (bitmapConfig == null) {
                throw new IllegalArgumentException("Bitmap config must not be null.");
            }
            this.defaultBitmapConfig = bitmapConfig;
            return this;
        }

        /**
         * Start building a new {@link Picasso} instance.
         *
         * @param context the context
         */
        public Builder(Context context) {
            if (context == null) {
                throw new IllegalArgumentException("Context must not be null.");
            }
           // this.context = context.getApplicationContext();
            this.context=context;
        }

        /**
         * Create the {@link Picasso} instance.
         *
         * @return the picasso
         */
        public Picasso build() {
            Context context = this.context;

            if (downloader == null) {
                downloader = new OkHttp3Downloader(context);
            }
            if (cache == null) {
                cache = new LruCache(context);
            }
            if (service == null) {
                service = new PicassoExecutorService();
            }
            if (transformer == null) {
                transformer = RequestTransformer.IDENTITY;
            }

            Stats stats = new Stats(cache);

            Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

            return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
                    defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
        }
    }

    static final EventHandler HANDLER = new EventHandler(EventRunner.getMainEventRunner()) {
        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            switch (event.eventId) {
                case HUNTER_BATCH_COMPLETE: {
                    List<BitmapHunter> batch = (List<BitmapHunter>) event.object;
                    for (int i = 0, n = batch.size(); i < n; i++) {
                        BitmapHunter hunter = batch.get(i);
                        try {
                            hunter.picasso.complete(hunter);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                }
                case REQUEST_GCED: {
                    Action action = (Action) event.object;
                    if (action.getPicasso().loggingEnabled) {
                        log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected");
                    }
                    action.picasso.cancelExistingRequest(action.getTarget());
                    break;
                }
                case REQUEST_BATCH_RESUME:
                    List<Action> batch = (List<Action>) event.object;
                    for (int i = 0, n = batch.size(); i < n; i++) {
                        Action action = batch.get(i);
                        try {
                            action.picasso.resumeAction(action);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                default:
                    throw new AssertionError("Unknown handler message received: " + event.eventId);
            }
        }
    };

    /**
     * Complete.
     *
     * @param hunter the bitmaphunter value
     * @throws RemoteException the remote exception
     */
    void complete(BitmapHunter hunter) throws RemoteException {
         Action single = hunter.getAction();
         List<Action> joined = hunter.getActions();

         boolean hasMultiple = joined != null && !joined.isEmpty();
         boolean shouldDeliver = single != null || hasMultiple;

         if (!shouldDeliver) {
             return;
         }

         Uri uri = hunter.getData().uri;

         Exception exception = hunter.getException();
         PixelMap result = hunter.getResult();
         LoadedFrom from = hunter.getLoadedFrom();

         if (single != null) {
             deliverAction(result, from, single, exception);
         }

         if (hasMultiple) {
             for (int i = 0, n = joined.size(); i < n; i++) {
                 Action join = joined.get(i);
                 deliverAction(result, from, join, exception);
             }
         }

         if (listener != null && exception != null) {
             listener.onImageLoadFailed(this, uri, exception);
         }
     }

    private void deliverAction(PixelMap result, LoadedFrom from, Action action, Exception e) throws RemoteException {
         if(action.isCancelled()) {
             return;
         }
         if (!action.willReplay()) {
             targetToAction.remove(action.getTarget());
         }
         if (result != null) {
             if (from == null) {
                 throw new AssertionError("LoadedFrom cannot be null.");
             }
             action.complete(result, from);
             if (loggingEnabled) {
                 log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
             }
         } else {
             action.error(e);
             if (loggingEnabled) {
                 log(OWNER_MAIN, VERB_ERRORED, action.request.logId(), e.getMessage());
             }
         }
     }

    /**
     * Resume action.
     *
     * @param action the action value
     * @throws RemoteException the remote exception
     */
    void resumeAction(Action action) throws RemoteException {
        PixelMap bitmap = null;

        if (shouldReadFromMemoryCache(action.memoryPolicy)) {
            bitmap = quickMemoryCacheCheck(action.getKey());
        }

        if (bitmap != null) {
            // Resumed action is cached, complete immediately.
            deliverAction(bitmap, MEMORY, action, null);
            if (loggingEnabled) {
                log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + MEMORY);
            }
        } else {
            // Re-submit the action to the executor.
            enqueueAndSubmit(action);
            if (loggingEnabled) {
                log(OWNER_MAIN, VERB_RESUMED, action.request.logId());
            }
        }
    }

    /**
     * Describes where the image was loaded from.
     */
    public enum LoadedFrom {
        MEMORY(Color.GREEN.getValue()),
        DISK(Color.BLUE.getValue()),
        NETWORK(Color.RED.getValue());

        final int debugColor;

        LoadedFrom(int debugColor) {
            this.debugColor = debugColor;
        }
    }
    /**
     * The priority of a request.
     * <p>
     * priority(Priority)
     */
    public enum Priority {
        LOW,
        NORMAL,
        HIGH
    }

    /**
     * Defer.
     *
     * @param view    the view
     * @param request the request
     */
    void defer(Image view, DeferredRequestCreator request) {
        // If there is already a deferred request, cancel it.
        if (targetToDeferredRequestCreator.containsKey(view)) {
            cancelExistingRequest(view);
        }
        targetToDeferredRequestCreator.put(view, request);
    }

    /**
     * Quick memory cache check pixel map.
     *
     * @param key the key
     * @return the pixel  map
     */
    PixelMap quickMemoryCacheCheck(String key) {
        PixelMap cached = cache.get(key);
        if (cached != null) {
            stats.dispatchCacheHit();
        } else {
            stats.dispatchCacheMiss();
        }
        return cached;
    }

    /**
     * Cancel any existing requests for the specified target {}.
     *a
     * @param view the view
     */
    public void cancelRequest(Image view) {
        if (view == null) {
           throw new IllegalArgumentException("view cannot be null.");
        }
        cancelExistingRequest(view);
    }

    /**
     * Cancel existing request.
     *
     * @param target the target
     */
    void cancelExistingRequest(Object target) {
        checkMain();
        Action action = targetToAction.remove(target);
        if (action != null) {
            action.cancel();
            dispatcher.dispatchCancel(action);
        }
        if (target instanceof Image) {
            Image targetImageView = (Image) target;
            DeferredRequestCreator deferredRequestCreator =
                    targetToDeferredRequestCreator.remove(targetImageView);
            if (deferredRequestCreator != null) {
                deferredRequestCreator.cancel();
            }
        }
    }

    /**
     * Cancel any existing requests for the specified {@link Target} instance.
     *
     * @param target the target
     */
    public void cancelRequest(Target target) {
        if (target == null) {
            throw new IllegalArgumentException("target cannot be null.");
        }
        cancelExistingRequest(target);
    }


    /**
     * Enqueue and submit.
     *
     * @param action the action
     */
    void enqueueAndSubmit(Action action) {
        Object target = action.getTarget();
        if (target != null && targetToAction.get(target) != action) {
            // This will also check we are on the main thread.
            cancelExistingRequest(target);
            targetToAction.put(target, action);
        }
        submit(action);
    }

    /**
     * Submit.
     *
     * @param action the action value
     */
    void submit(Action action) {
        dispatcher.dispatchSubmit(action);
    }

    /**
     * Transform request request.
     *
     * @param request the request data
     * @return the request data
     */
    Request transformRequest(Request request) {
        Request transformed = requestTransformer.transformRequest(request);
        if (transformed == null) {
            throw new IllegalStateException("Request transformer "
                    + requestTransformer.getClass().getCanonicalName()
                    + " returned null for "
                    + request);
        }
        return transformed;
    }


    /**
     * Start an image request using the specified URI.
     * <p>
     * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,
     * if one is specified.
     * <p>
     * #load(File)
     * #load(String)
     * #load(int)
     *
     * @param uri the uri
     * @return the request creator
     */
    public RequestCreator load( Uri uri) {
        return new RequestCreator(this, uri, 0);
    }

    /**
     * Start an image request using the specified path. This is a convenience method for calling
     * { #load(Uri)}.
     * <p>
     * This path may be a remote URL, file resource (prefixed with {@code file:}), contengetgt resource
     * (prefixed with {@code content:}), or ohos resource (prefixed with {@code ohos.global.resource:}.
     * <p>
     * Passing {@code null} as a {@code path} will not trigger any request but will set a
     * placeholder, if one is specified.
     * <p>
     * #load(Uri)
     * #load(File)
     * #load(int)
     *
     * @param path the path
     * @return the request creator
     * @throws IllegalArgumentException if {@code path} is empty or blank string.
     */
    public RequestCreator load( String path) {
        if (path == null) {
            return new RequestCreator(this, null, 0);
        }
        if (path.trim().length() == 0) {
            throw new IllegalArgumentException("Path must not be empty.");
        }
        return load(Uri.parse(path));
    }

    /**
     * Cancel any existing requests for the specified {link RemoteViews} target with the given {@code
     * viewId}*********************.
     *
     * @param remoteViews the remote views
     * @param viewId      the view id
     */
    public void cancelRequest(ComponentProvider remoteViews, int viewId) {
        if (remoteViews == null) {
            throw new IllegalArgumentException("remoteViews cannot be null.");
        }
    }

    /**
     * Cancel any existing requests with given tag. You can set a tag
     * on new requests with {@link RequestCreator#tag(Object)}.
     *
     * @param tag the tag
     * @see RequestCreator#tag(Object)
     */
    public void cancelTag( Object tag) {
        checkMain();
        if (tag == null) {
            throw new IllegalArgumentException("Cannot cancel requests with null tag.");
        }

        List<Action> actions = new ArrayList<>(targetToAction.values());
        for (int i = 0, n = actions.size(); i < n; i++) {
            Action action = actions.get(i);
            if (tag.equals(action.getTag())) {
                cancelExistingRequest(action.getTarget());
            }
        }

        List<DeferredRequestCreator> deferredRequestCreators =
                new ArrayList<>(targetToDeferredRequestCreator.values());
        for (int i = 0, n = deferredRequestCreators.size(); i < n; i++) {
            DeferredRequestCreator deferredRequestCreator = deferredRequestCreators.get(i);
            if (tag.equals(deferredRequestCreator.getTag())) {
                deferredRequestCreator.cancel();
            }
        }
    }

    /**
     * Pause existing requests with the given tag. Use {@lik #resumeTag(Object)}
     * to resume requests with the given tag.
     *
     * @param tag the tag
     * @se resumeTag(Object)
     * @see RequestCreator#tag(Object)
     */
    public void pauseTag( Object tag) {
        if (tag == null) {
            throw new IllegalArgumentException("tag == null");
        }
        dispatcher.dispatchPauseTag(tag);
    }

    /**
     * Resume paused requests with the given tag. Use {@link #pauseTag(Object)}
     * to pause requests with the given tag.
     *
     * @param tag the tag value
     * @see #pauseTag(Object)
     * @see RequestCreator#tag(Object)
     */
    public void resumeTag( Object tag) {
        if (tag == null) {
            throw new IllegalArgumentException("tag == null");
        }
        dispatcher.dispatchResumeTag(tag);
    }

    /**
     * Start an image request using the specified image file. This is a convenience method for
     * calling {@link #load(Uri)}.
     * <p>
     * Passing {@code null} as a {@code file} will not trigger any request but will set a
     * placeholder, if one is specified.
     * <p>
     * Equivalent to calling {@link #load(Uri) load(Uri.fromFile(file))}.
     *
     * @param file the file
     * @return the request creator
     * @see #load(Uri) #load(Uri)
     * @see #load(String) #load(String)
     * @see #load(int) #load(int)#load(int)
     */
    public RequestCreator load( File file) {
        if (file == null) {
            return new RequestCreator(this, null, 0);
        }
        return load(Uri.getUriFromFile(file));
    }

    /**
     * Start an image request using the specified drawable resource ID.
     *
     * @param resourceId the resource id value
     * @return the request creator
     * @see #load(Uri) #load(Uri)
     * @see #load(String) #load(String)
     * @see #load(File) #load(File)#load(File)
     */
    public RequestCreator load( int resourceId) {
        if (resourceId == 0) {
            throw new IllegalArgumentException("Resource ID must not be zero.");
        }
        return new RequestCreator(this, null, resourceId);
    }

    /**
     * Invalidate all memory cached images for the specified {@code uri}.
     *
     * @param uri the uri value
     * @se #invalidate(String)
     * @se #invalidate(File)
     */
    public void invalidate( Uri uri) {
        if (uri != null) {
            cache.clearKeyUri(uri.toString());
        }
    }

    /**
     * Invalidate all memory cached images for the specified {@code path}. You can also pass a
     * {@linkplain RequestCreator#stableKey stable key}.
     *
     * @param path the path
     * @ee #invalidate(File)
     * @see #invalidate(Uri) #invalidate(Uri)#invalidate(Uri)
     */
    public void invalidate( String path) {
        if (path != null) {
            invalidate(Uri.parse(path));
        }
    }

    /**
     * Invalidate all memory cached images for the specified {@code file}.
     *
     * @param file the file
     * @see #invalidate(Uri) #invalidate(Uri)
     * @see #invalidate(String) #invalidate(String)
     */
    public void invalidate( File file) {
        if (file == null) {
            throw new IllegalArgumentException("file == null");
        }
        invalidate(Uri.getUriFromFile(file));
    }

    /**
     * Toggle whether to display debug indicators on images.
     *
     * @param enabled the enabled
     */
    @SuppressWarnings("UnusedDeclaration") public void setIndicatorsEnabled(boolean enabled) {
        indicatorsEnabled = enabled;
    }

    /**
     * {@code true} if debug indicators should are displayed on images.
     *
     * @return the boolean
     */
    @SuppressWarnings("UnusedDeclaration") public boolean areIndicatorsEnabled() {
        return indicatorsEnabled;
    }

    /**
     * Toggle whether debug logging is enabled.
     * <p>
     * <b>WARNING:</b> Enabling this will result in excessive object allocation. This should be only
     * be used for debugging Picasso behavior. Do NOT pass {@code BuildConfig.DEBUG}.
     *
     * @param enabled the enabled
     */
    @SuppressWarnings("UnusedDeclaration") // Public API.
    public void setLoggingEnabled(boolean enabled) {
        loggingEnabled = enabled;
    }

    /**
     * {@code true} if debug logging is enabled.
     * @return the boolean
     */
    public boolean isLoggingEnabled() {
        return loggingEnabled;
    }

    /**
     * Creates a {@link StatsSnapshot} of the current stats for this instance.
     * <p>
     * <b>NOTE:</b> The snapshot may not always be completely up-to-date if requests are still in
     * progress.
     *
     * @return the snapshot
     */
    @SuppressWarnings("UnusedDeclaration") public StatsSnapshot getSnapshot() {
        return stats.createSnapshot();
    }

    /**
     * Stops this instance from accepting further requests.
     */
    public void shutdown() {
        if (this == singleton) {
            throw new UnsupportedOperationException("Default singleton instance cannot be shutdown.");
        }
        if (shutdown) {
            return;
        }
        cache.clear();
        // cleanupThread.shutdown();
        stats.shutdown();
        dispatcher.shutdown();
        for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) {
            deferredRequestCreator.cancel();
        }
        targetToDeferredRequestCreator.clear();
        shutdown = true;
    }
}